#include <gmpxx.h>
#include <iostream>
#include <cstdlib>
#include <fstream>

#include "Polynome.hpp"


using namespace std;

Polynome::Polynome(char * inNb, int inNbDigits)
{
   degre(inNbDigits);
   _tabCoef = new mpz_class[_d + 1];
}


Polynome::~Polynome()
{
   delete [] _tabCoef;
}


void Polynome::degre(int inNbDigits)
{
   if(inNbDigits < 80)
   {
      _d = 3;
   }
   else
   {
      _d = 5;
   }
}


void Polynome::base(mpz_class inN)
{
   mpz_root(_m.get_mpz_t(), inN.get_mpz_t(), _d);
}


void Polynome::coef(mpz_class inN)
{
   mpz_class reste;
   mpz_class base;
   mpz_class a;
   //Premier coef traité séparément
   reste = 1;

   insert(0, reste);
   
   mpz_pow_ui(base.get_mpz_t(), _m.get_mpz_t(), _d);        //base = m^d
   reste = inN - base;
   
   for(int i = _d - 1 ; i > 0 ; i--)
   {
      mpz_pow_ui(base.get_mpz_t(), _m.get_mpz_t(), i);        //base = m^i
      a = (mpz_class) reste / base;                          
      
      insert( _d - i, a);
      reste -= a * base;                                    //On recalcul le reste
   }
   insert(_d, reste);
}

void Polynome::recherchePoly(mpz_class inN)
{
   base(inN);
   coef(inN);
   
   if(!verifPoly(inN))
   {
      cout << "Erreur de polynome" << endl;
      exit(-1);
   }
}




mpz_class Polynome::eval(mpz_class inVal) const
{
   mpz_class nombre = 0;
   mpz_class base;
   
   for(int i = 0 ; i < _d + 1 ; i++)
   {
      mpz_pow_ui(base.get_mpz_t(), inVal.get_mpz_t(), _d - i);    // inVal^(d - i)
      
      nombre += _tabCoef[ i ] * base;
      
   }
   return nombre;
}

mpz_class Polynome::evalFPrime(mpz_class & inVal)
{
   mpz_class nombre = 0;
   mpz_class base;
   
   for(int i = 0 ; i < _d ; i++)
   {
      mpz_pow_ui(base.get_mpz_t(), inVal.get_mpz_t(), _d - i - 1);
      
      nombre += _tabCoef[ i ] * base * (_d - i);
   }
   return nombre;
}

Polynome * Polynome::FPrimeCarreModF()
{
   //mpz_class nombre = 0;
   int degFPrimeCarre = 2 * _d - 2;
         
   mpz_class * fPrime = new mpz_class[_d];
   
   mpz_class * fPrimeCarre = new mpz_class[degFPrimeCarre + 1];
   
   mpz_class * fPrimeModF = new mpz_class[_d];
   
   // Calcul de f'
   for(int i = 0 ; i < _d ; i++)
   {
      fPrime[i] = _tabCoef[i] * (_d - i);
      cout << "fprime[" << i << "] = " << fPrime[i] << endl;
   }
   cout << "fin calcul fPrime" << endl;
   
   // Initialisation des coeficent de f' ^ 2
   for(int i = 0 ; i < degFPrimeCarre + 1 ; i++)
   {
      fPrimeCarre[i] = 0;
   }
   cout << "fin init fprimecarré" << endl;
   
   // Construction de f' ^ 2 
   for(int i = 0 ; i < _d ; i++)
   {
      for(int j = 0 ; j < _d ; j++)
      {
         fPrimeCarre[i + j] += fPrime[i] * fPrime[j];
      }
   }
   cout << "calucl f'carre" << endl;
   
   mpz_class coef;
   
   for(int i = 0  ; i < degFPrimeCarre + 1 - _d ; i++)
   {
      coef = fPrimeCarre[i];
      
      for(int j = 0 ; j < _d + 1 ; j++)
      {
         fPrimeCarre[i + j] -= coef * _tabCoef[j];
      }
   }
   cout << "calcul de f'carré mod f" << endl;
   
   // Remplissage de f'² (mod f)
   for(int i = 0 ; i < _d ; i++)
   {
      cout << " i = " << i << endl;
      fPrimeModF[_d - 1 - i] = fPrimeCarre[degFPrimeCarre - i];
   }
   cout << "remplissage de f'carre mod f" << endl;
   

   Polynome * pfPrimModF = new Polynome(fPrimeModF, _d - 1);
   
   return pfPrimModF;
}

Polynome * Polynome::powPModF(int p)
{
   mpz_class * tabCoef = new mpz_class[p + 1];
   mpz_class * res = new mpz_class[_d];

   mpz_class coef;
   mpz_class P = p;
   mpz_class q;
   mpz_class qTmp;
   mpz_class qp;
   mpz_class abs;

   for(int i = 1 ; i < _d ; i++)
   {
      tabCoef[i] = 0;
   }
   tabCoef[0] = 1;
   tabCoef[p - 1] = -1;
   
   for(int i = p ; i >= 3 ; i--)
   {
      coef = tabCoef[p - i];
      
      for(int j = 0 ; j < _d + 1; j++)
      {
         tabCoef[p - i + j] -= coef * _tabCoef[j];
      }
   }
   
   for(int j = 0 ; j < p + 1 ; j++)
   {
      mpz_mod(tabCoef[j].get_mpz_t(), tabCoef[j].get_mpz_t(), P.get_mpz_t());
   }
   
   for(int j = p - 2 ; j < p + 1 ; j++)
   {
      res[j - (p - 2)] = tabCoef[j];
   }
   
   Polynome * pModF = new Polynome(res, _d - 1);
   
   cout << "resultat : " << *pModF << endl;
   
   return pModF;
}

mpz_class Polynome::bpowdf(mpz_class & a, mpz_class & b)
{
   mpz_class nb;
   mpz_class apowd_i;
   mpz_class _bpowi;
   int signe = -1;
   
   for(int i = 0 ; i < _d + 1 ; i++)
   {
      signe *= -1;
      mpz_pow_ui(apowd_i.get_mpz_t(), a.get_mpz_t(), _d-i);    // a^(d-i)
      
      mpz_pow_ui(_bpowi.get_mpz_t(), b.get_mpz_t(), i);
      
      nb += _tabCoef[ i ] * apowd_i * _bpowi * signe;
   }
   
   return nb;
}

void Polynome::generateFile() const
{
   ofstream fic;
   
   fic.open("notregnfs.poly", ios::out);
   
   if(fic.bad())
   {
      exit(-1);
   }
   
   
   // Remplissage du polynome
   fic << "[";
   
   for(int i = 0 ; i < _d ; i++)
   {
      fic << _tabCoef[_d - i];
      fic << " ";
   }
   
   fic << _tabCoef[0];
   fic << "]" << endl;
   
   // m
   fic << _m << endl;
   
   fic << -1 << endl;
   fic << 0 << endl;
   
   fic.close();
}

ostream & operator << (ostream & o,const Polynome & inPoly)
{
   for(int i = 0 ; i <= inPoly._d ; i++)
   {
      o << "Coefficient "<< i <<" : "; 
      o << inPoly._tabCoef[i] << endl;
   }
   return o;
}















